#include <SymbolTable.h>
#include <string>
#include <iostream>
#include <netthumb.h>
#include <BackendI.h>
#include <sstream>
#include <map>
#include <vector>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fstream>
#include <stdio.h>

namespace netthumb {

  BackendI::BackendI(const string& fileName,
		     const string& outFormat,
		     SymbolTable& stable):
    _sTable(stable),
    _outType(outFormat)
  {
    int infIndex = fileName.find_last_of("/") == string::npos ? 0 : fileName.find_last_of("/") + 1;
    _fileName = fileName.substr(infIndex, _fileName.size() - infIndex);
    int supIndex = _fileName.find_first_of(".") == string::npos ? _fileName.size() - 1 : _fileName.find_first_of(".");
    _fileName = _fileName.substr(0, supIndex);
  }

  string
  BackendI::generateDevice(string devName, string systemName, DeviceI* device)
  {
    ostringstream devStream;

    map<LinkSt, string> linkStMap;
    map<NetScope, string> netScopeMap;

    linkStMap[nullSt] = "";
    linkStMap[up] = "up";
    linkStMap[down] = "down";

    netScopeMap[nullScope] = "";
    netScopeMap[global] = "global";
    netScopeMap[site] = "site";
    netScopeMap[link] = "link";
    netScopeMap[host] = "host";

    // Generacion del icono      
    devStream << "\t" << systemName << "_" << devName << "_icon " << NIC_ICON << std::endl;
    
    // Generacion del cluster
    ostringstream label;
    label << devName << "\\nLink: " << device->getLinkID() << " " << linkStMap[device->getLinkSt()];
    
    devStream << "\t" << "subgraph cluster_" << systemName << "_" << devName << " {" << CLUSTER_ATTR << "; ";
    devStream << "label=\"" << label.str() << "\"; " << systemName << "_" << devName << "_icon};" << std::endl;
    
    // Generacion del enlace
    devStream << "\t" << "cluster_" << systemName << " -> " << "cluster_" << systemName << "_" << devName;
    devStream << " " << "[" << LINK_ATTR << "]" << ";" << std::endl << std::endl;

    // Generacion del enlace con la red.
    ostringstream linkLabel;
    linkLabel << "Addr: " << device->getAddressID();
    if(device->getScope() != nullScope) 
      linkLabel << "\\nscope " << netScopeMap[device->getScope()];

    _linkStream << "\tcluster_" << device->getNetID() << " -> ";
    _linkStream << "cluster_" << systemName << "_" << devName << " [" << LINK_ATTR << ", fontsize=8, label=\"";
    _linkStream << linkLabel.str() << "\"];" << std::endl;
    
    return devStream.str();
  }

  string
  BackendI::generateHosts()
  {

    ostringstream hostStream;
    map<string, HostI*>::iterator hostIt;

    for(hostIt = _sTable.getHosts().begin(); hostIt != _sTable.getHosts().end(); hostIt++) {

      // Generacion del icono.
      hostStream << "\t" << hostIt->first << "_icon " << HOST_ICON << std::endl;

      // Generacion del nodo.
      hostStream << "\t" << "subgraph cluster_" << hostIt->first << " {" << CLUSTER_ATTR << "; " ;
      hostStream << "label=\"" << hostIt->first << "\"; " << hostIt->first << "_icon};" << std::endl << std::endl;
      hostStream << generateDevicesFromHost(hostIt->first) << std::endl;

      // Generacion de las lineas para la tabla de rutas.
      generateRouteLines(hostIt->first, hostIt->second->getRoute());
    }

    return hostStream.str();
  }

  void
  BackendI::generateRouteLines(string host, RouteI* route)
  {
    // Generacion de la linea correspondiente en la tabla de rutas.

    map<NetType, string> netTypeMap;

    netTypeMap[nullType] = "";
    netTypeMap[unicast] = "unicast";
    netTypeMap[unreachable] = "unreachable";
    netTypeMap[blackhole] = "blackhole";
    netTypeMap[prohibit] = "prohibit";
    netTypeMap[local] = "local";
    netTypeMap[broadcast] = "broadcast";
    netTypeMap[thrown] = "thrown";
    netTypeMap[nat] = "nat";
    netTypeMap[anycast] = "anycast";
    netTypeMap[multicast] = "multicast";

    // Ruta por defecto
    _rTContentStream << host << " & " << route->getDeviceID() << " & & " << route->getViaAddr();
    if(route->getMetricOrPriority())
      _rTContentStream << " & & " << route->getMetric() << " & ";
    else
      _rTContentStream << " & " << route->getMetric() << " & & ";
    _rTContentStream << " & YES\\\\\\hline" << endl;

    // Resto de las rutas.
    vector<ConfRouteI*>::iterator it;
    for(it = route->getConfigurations().begin(); 
	it != route->getConfigurations().end(); 
	it++){

      _rTContentStream << host << " & " << (*it)->getDeviceID() << " & " << (*it)->getNetID() << " & " << (*it)->getViaAddr();
      if((*it)->getMetricOrPriority())
      _rTContentStream << " & & " << (*it)->getMetric() << " & ";
      else
	_rTContentStream << " & " << (*it)->getMetric() << " & & ";
    _rTContentStream << netTypeMap[(*it)->getNetType()] << " & NO\\\\\\hline" << endl;
      
    }
  }

  string
  BackendI::generateDevicesFromHost(string hostName)
  {
    ostringstream devStream;

    map<LinkSt, string> linkStMap;
    map<NetScope, string> netScopeMap;

    linkStMap[nullSt] = "";
    linkStMap[up] = "up";
    linkStMap[down] = "down";

    netScopeMap[nullScope] = "";
    netScopeMap[global] = "global";
    netScopeMap[site] = "site";
    netScopeMap[link] = "link";
    netScopeMap[host] = "host";

    map<string, DeviceI*> deviceMap;
    _sTable.getDevicesByHost(hostName, deviceMap);

    map<string, DeviceI*>::iterator devIt;
    for(devIt = deviceMap.begin(); devIt != deviceMap.end(); devIt++) {
      devStream << generateDevice(devIt->first, hostName, devIt->second);
    }

    return devStream.str();
  }

  string
  BackendI::generateRouters()
  {

    ostringstream routerStream;

    map<string, RouterI*>::iterator routerIt;

    for(routerIt = _sTable.getRouters().begin(); routerIt != _sTable.getRouters().end(); routerIt++) {

      // Generacion del icono.
      routerStream << "\t" << routerIt->first << "_icon " << ROUTER_ICON << std::endl;

      // Generacion del nodo.
      routerStream << "\t" << "subgraph cluster_" << routerIt->first << " {" << CLUSTER_ATTR << "; " ;
      routerStream << "label=\"" << routerIt->first << "\"; " << routerIt->first << "_icon};" << std::endl << std::endl;
      routerStream << generateDevicesFromRouter(routerIt->first) << std::endl;

      // Generacion de las lineas para la tabla de rutas.
      generateRouteLines(routerIt->first, routerIt->second->getRoute());
    }
    return routerStream.str();
  }
   
  string
  BackendI::generateDevicesFromRouter(string routerName)
  {
    ostringstream devStream;

    map<LinkSt, string> linkStMap;
    map<NetScope, string> netScopeMap;

    linkStMap[nullSt] = "";
    linkStMap[up] = "up";
    linkStMap[down] = "down";

    netScopeMap[nullScope] = "";
    netScopeMap[global] = "global";
    netScopeMap[site] = "site";
    netScopeMap[link] = "link";
    netScopeMap[host] = "host";

    map<string, DeviceI*> deviceMap;
    _sTable.getDevicesByRouter(routerName, deviceMap);

    map<string, DeviceI*>::iterator devIt;
    for(devIt = deviceMap.begin(); devIt != deviceMap.end(); devIt++) {
      devStream << generateDevice(devIt->first, routerName, devIt->second);
    }

    return devStream.str();    
  }

  string
  BackendI::generateNets()
  {

    ostringstream netStream;
    map<string, NetworkI*>::iterator netIt;

    for(netIt = _sTable.getNetworks().begin(); netIt != _sTable.getNetworks().end(); netIt++) {
      // Generacion del icono.
      netStream << "\t" << netIt->first << "_icon " << NET_ICON << std::endl;
      
      netStream << "\t" << "subgraph cluster_" << netIt->first << " {" << CLUSTER_ATTR << "; " ;
      netStream << "label=\"" << netIt->first << "\"; " << netIt->first << "_icon};" << std::endl << std::endl;

      // Generacion de una linea para la tabla de redes.
      _nTContentStream << netIt->first << " & " << netIt->second->getAddress() << " & ";
      _nTContentStream << netIt->second->getTypeID() << " & " << netIt->second->getMtu();
      if(netIt->second->isMulticast())
	_nTContentStream << " & ON\\\\\\hline" << std::endl;
      else
	_nTContentStream << " & OFF\\\\\\hline" << std::endl;
    }
    return netStream.str();
  }

  void
  BackendI::makeTables()
  {

    // Tabla de redes.
    ostringstream netStream;
    netStream << NET_TABLE_PREAM << _nTContentStream.str();
    netStream << "\\caption{Tabla de redes correspondiente a \\emph{" << _fileName << "}}" << std::endl;
    netStream << "\\label{tbl:net_" << _fileName << "}" << std::endl;
    netStream << "\\end{tabular}" << std::endl;

    string netFileName = string("net_") + _fileName + string(".tex");

    // Guarda la tabla.
    ofstream netFileStream(netFileName.c_str());
    netFileStream << netStream.str();
    netFileStream.flush();
    netFileStream.close();
    
    // Tabla de rutas.
    ostringstream routeStream;
    routeStream << ROUTE_TABLE_PREAM << _rTContentStream.str();
    routeStream << "\\caption{Tabla de rutas correspondiente a \\emph{" << _fileName << "}}" << std::endl;
    routeStream << "\\label{tbl:route_" << _fileName << "}" << std::endl;
    routeStream << "\\end{tabular}" << std::endl;

    string routeFileName = string("route_") + _fileName + string(".tex");

    // Guarda la tabla.
    ofstream routeFileStream(routeFileName.c_str());
    routeFileStream << routeStream.str();
    routeFileStream.flush();
    routeFileStream.close();
  }

  void
  BackendI::makeGraph()
  {
    _dotStream << "digraph " << _fileName << "{" << std::endl << std::endl;
    _dotStream << GRAPH_PREAM_0 << _fileName << GRAPH_PREAM_1 << std::endl << std::endl;
    _dotStream << generateHosts() << generateRouters() << generateNets() << std::endl;
    _dotStream << _linkStream.str() << std::endl;
    _dotStream << "}" << std::endl;

    string tmpFileName = _fileName + string(".tmp");
    string outFileName = _fileName + "." + _outType;
    char* nullP = 0;

    // Genera un archivo temporal para la generacion del grafico
    ofstream tmpFileStream(tmpFileName.c_str());
    tmpFileStream << _dotStream.str();
    tmpFileStream.flush();
    tmpFileStream.close();

    int pid = fork();
    if(pid == 0){
      // Genera el grafico.
      execlp(GRAPH_CMD, GRAPH_CMD, "-T", _outType.c_str(), "-o" , outFileName.c_str(), tmpFileName.c_str(), nullP);
    } else {
      int v;
      // Genera las tablas.
      makeTables();
      wait(&v);
      // Elimina el archivo temporal.
      remove(tmpFileName.c_str());
    }
  }

  void
  BackendI::genCmdFile(const string& fileName)
  {
    ofstream fileStream(fileName.c_str());
    fileStream << _dotStream.str();
    fileStream.flush();
    fileStream.close();
  }
}
